#include "stdafx.h"
#include "mt4dll.h"

#include <stdio.h>
#include <time.h>

#define MT4DLL_API __declspec(dllexport)

#define	MT4DLL_VERSION 0x00000002
#define SHARE_MEM_NAME "MT4SHAREMEMORY"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
    return TRUE;
}

typedef struct MsgRcrd
{
	int			tick;
	char		msg[1020];
} MSGRCRD, * PMSGRCRD;

#pragma warning(disable:4200)
typedef struct QueueHead
{
	int			version;
	unsigned	length;
	char		padding[1016];
	MSGRCRD		queue[];
} QUEUEHEAD, * PQUEUEHEAD;
#pragma warning(default:4200) 

//-----------------------------------------------------------------
//Datastructure for Server
//-----------------------------------------------------------------

FILE * logfile;

HANDLE		hMapFile;
PMSGRCRD	pQueue;
PQUEUEHEAD	pHead;
unsigned	iCurPos;
BOOL		bInited;

//-----------------------------------------------------------------
//Datastructure for Client
//-----------------------------------------------------------------

//-----------------------------------------------------------------
//Functions for Server
//-----------------------------------------------------------------
static int __stdcall SendOneMsg(MqlStr * msg);

//-----------------------------------------------------------------
//Server initialization
//Return:   0 if OK
//			-1 if has been initialized
//			-2 if Could not open file mapping object
//			-3 if Could not map view of file
//			-4 if queuelen is not appropriate
//-----------------------------------------------------------------
MT4DLL_API int __stdcall ServerInit(unsigned queuelen, BOOL logging)
{
	if(bInited) {
		logfile && fprintf(logfile, "Could not init again, call ServerUnInit() first.\n");
		return -1;
	}

	if(queuelen < 64 || queuelen > 1024 * 8){
		logfile && fprintf(logfile, "queuelen must between %u and %u.\n", 64, 1024*8);
		return -4;
	}
	if(logging){
		errno_t err;
		// open log file
		if ( (err = fopen_s(&logfile, "mt4dll.log","a")) != 0 ){
			MessageBox(NULL,TEXT("Could not open log file"),TEXT("MT4DLL"),NULL);
		}
	}

	// mapping memory
	hMapFile = CreateFileMapping(
		INVALID_HANDLE_VALUE,    // use paging file
		NULL,                    // default security 
		PAGE_READWRITE,          // read/write access
		0,                       // max. object size 
		sizeof(QUEUEHEAD) + queuelen*sizeof(MSGRCRD),                // buffer size  
		SHARE_MEM_NAME);                 // name of mapping object

	if (hMapFile == NULL || hMapFile == INVALID_HANDLE_VALUE) { 
		logfile && fprintf(logfile, "Could not create file mapping object (%d).\n", 
			GetLastError());
		bInited = FALSE;
		return -2;
	}

	pHead = (PQUEUEHEAD) MapViewOfFile(hMapFile,		// handle to map object
		FILE_MAP_ALL_ACCESS,					// read/write permission
		0,
		0,
		sizeof(QUEUEHEAD) + queuelen*sizeof(MSGRCRD));           

	if (pHead == NULL) { 
		logfile && fprintf(logfile, "Could not map view of file (%d).\n", 
			GetLastError());
		CloseHandle(hMapFile);
		bInited = FALSE;
		return -3;
	}

	pHead->version = MT4DLL_VERSION;
	pHead->length = queuelen;
	pQueue = pHead->queue;

	// init
	ZeroMemory(pQueue, queuelen*sizeof(MSGRCRD));
	iCurPos = 0;
	bInited = TRUE;
	logfile && fprintf(logfile, "Server is running, queue length is: %d.\n", pHead->length);
	return 0;
}

//-----------------------------------------------------------------
//Server uninitialization
//-----------------------------------------------------------------
MT4DLL_API void __stdcall ServerUnInit(void)
{
	UnmapViewOfFile(pHead);
	pHead = NULL;

	CloseHandle(hMapFile);
	hMapFile = INVALID_HANDLE_VALUE;
	
	bInited = FALSE;

	logfile && fprintf(logfile, "Server shutdown.\n");
		// close log file
	logfile && fclose(logfile);
	logfile = NULL;
}



//-----------------------------------------------------------------
//Send msgs to client
//First accept for new clients, then send msg to all clients
//Return:	0 if OK
//			-1 if not initialized
//			-2 if message is too large
//-----------------------------------------------------------------
MT4DLL_API int __stdcall Send(MqlStr * msg,const int len)
{
	int i, ret;

	for(i=0; i<len; i++) {
		ret = SendOneMsg(&msg[i]);
		if(ret) {
			return ret;
		}
	}
	return 0;
}

//-----------------------------------------------------------------
//Send one msg to client
//First accept for new clients, then send msg to all clients
//Return:	0 if OK
//			-1 if not initialized
//			-2 if message is too large
//-----------------------------------------------------------------
static int __stdcall SendOneMsg(MqlStr * msg)
{
	PMSGRCRD pCur, pNext;
	unsigned iNextPos;

	if(!bInited) {
		logfile && fprintf(logfile, "Call ServerInit() first.\n");
		return -1;
	}
	if(strlen(msg->string)+1 >= sizeof(((PMSGRCRD)0)->msg)) {
		logfile && fprintf(logfile, "Message too large(>%d).\n", sizeof(((PMSGRCRD)0)->msg)-1);
		return -2;
	}

	//clear next record
	iNextPos = iCurPos+1;
	if(iNextPos>=pHead->length) {
		iNextPos = 0;
	}
	pNext = (PMSGRCRD)&(pQueue[iNextPos]);
	ZeroMemory(pNext, sizeof(MSGRCRD));

	//clear current record
	pCur = (PMSGRCRD)&(pQueue[iCurPos]);
	ZeroMemory(pCur, sizeof(MSGRCRD));
	//copy message
	strncpy_s(pCur->msg, sizeof(pCur->msg), msg->string, sizeof(pCur->msg));

	int tick = GetTickCount();
	pCur->tick = tick;

	iCurPos++;
	if(iCurPos>=pHead->length){
		iCurPos = 0;
	}
	logfile && fprintf(logfile, "Message send, current pos is: %d.\n", iCurPos);

	return 0;
}

//-----------------------------------------------------------------
//Client initialization
//Return:	0 if OK
//			-1 if has been initialized
//			-2 if Could not open file mapping object
//			-3 if Could not map view of file--phase 1
//			-4 if Version or queue length mismatch
//			-5 if Could not find start position
//			-6 if Could not map view of file--phase 2
//-----------------------------------------------------------------
MT4DLL_API int __stdcall ClientInit(BOOL logging)
{
	if(bInited) {
		logfile && fprintf(logfile, "Could not init again, call ClientUnInit() first.\n");
		return -1;
	}

	if(logging) {
		errno_t err;
		// open log file
		if ( (err = fopen_s(&logfile, "mt4dllclient.log","a")) != 0 ){
			MessageBox(NULL,TEXT("Could not open log file"),TEXT("MT4DLL"),NULL);
		}
	}

	// mapping memory
	hMapFile = OpenFileMapping(
		FILE_MAP_READ,					// read/write access
		FALSE,							// do not inherit the name
		SHARE_MEM_NAME);				// name of mapping object 

	if (hMapFile == NULL) { 
		logfile && fprintf(logfile, "Could not open file mapping object (%d).\n", 
			GetLastError());
		return -2;
	} 

	// map to get version and queuelen
	pHead = (PQUEUEHEAD)MapViewOfFile(hMapFile,		// handle to mapping object
		FILE_MAP_READ,								// read/write permission
		0,
		0,
		sizeof(QUEUEHEAD));                   

	if (pHead == NULL) { 
		logfile && fprintf(logfile, "Could not map view of file (%d)--phase 1.\n", 
			GetLastError()); 
		CloseHandle(hMapFile);
		return -3;
	}

	unsigned length;
	length = pHead->length;
	// map again
	UnmapViewOfFile(pHead);
	pHead = (PQUEUEHEAD)MapViewOfFile(hMapFile,		// handle to mapping object
		FILE_MAP_READ,								// read/write permission
		0,
		0,
		sizeof(QUEUEHEAD) + length*sizeof(MSGRCRD));                   

	if (pHead == NULL) { 
		logfile && fprintf(logfile, "Could not map view of file (%d)--phase 2.\n", 
			GetLastError()); 
		CloseHandle(hMapFile);
		return -6;
	}

	
	//version check
	logfile && fprintf(logfile, "Client version: %d, queue length: %d.\n", MT4DLL_VERSION, pHead->length);
	logfile && fprintf(logfile, "Server version: %d, queue length: %d.\n", pHead->version, pHead->length);
	if( pHead->version != MT4DLL_VERSION ||
		pHead->length != pHead->length ) {
		UnmapViewOfFile(pHead);
		CloseHandle(hMapFile);
		logfile && fprintf(logfile, "Version or queue length mismatch.\n");
		return -4;
	}
	pQueue = pHead->queue;

	{
		PMSGRCRD pCur;
		for(iCurPos=0;iCurPos<pHead->length;iCurPos++) {
			pCur = (PMSGRCRD)&pQueue[iCurPos];
			if( !(pCur->tick) ){
				break;
			}
		}
		if(iCurPos>=pHead->length){
			UnmapViewOfFile(pHead);
			CloseHandle(hMapFile);
			logfile && fprintf(logfile, "Could not find start position.\n");
			return -5;
		}
	}
	bInited = TRUE;
	logfile && fprintf(logfile, "Client started, queue length is: %d.\n", pHead->length);
	return 0;
}

//-----------------------------------------------------------------
//Client uninitialization
//-----------------------------------------------------------------
MT4DLL_API void __stdcall ClientUnInit(void)
{
	UnmapViewOfFile(pHead);

	CloseHandle(hMapFile);
	
	bInited = FALSE;

	logfile && fprintf(logfile, "Client shutdown.\n");
	// close log file
	logfile && fclose(logfile);
	logfile = NULL;
}

//-----------------------------------------------------------------
//Receive a msg from server
//Return:	0 if no data
//			>0 if succeed, 
//					return value is the number of record received
//			-1 if not initialized
//			-2 if mqlstr.string is NULL
//			-3 if mqlstr has not enough buf
//-----------------------------------------------------------------
MT4DLL_API int __stdcall Receive(MqlStr * mqlstr, int arraylen)
{
	PMSGRCRD pCur;
	unsigned iPos, count;
	char buf[sizeof((PMSGRCRD)0)->msg];

	if(!bInited) {
		logfile && fprintf(logfile, "Call ClientInit() first.\n");
		return -1;
	}
	if(!mqlstr) {
		logfile && fprintf(logfile, "Invalid parameter:mqlstr.string is NULL.\n");
		return -2;
	}
	
	iPos = iCurPos;
	count = 0;
	pCur = (PMSGRCRD)&pQueue[iPos];
	while(pCur->tick) {
		strncpy_s(buf, sizeof(buf), pCur->msg, sizeof(pCur->msg));
		
		size_t msglen = strlen(buf);
		if(mqlstr[count].len < 0 || (unsigned)mqlstr[count].len < msglen){
			logfile && fprintf(logfile, "Invalid parameter:mqlstr[%d] has not enough buf.\n", count);
			return -3;
		}

		strncpy_s(mqlstr[count].string, mqlstr[count].len, buf, msglen);

		logfile && fprintf(logfile, "Message received, current pos is: %d.\n", iPos);
		iPos++;
		if(iPos>=pHead->length){
			iPos = 0;
		}
		pCur = (PMSGRCRD)&pQueue[iPos];
		count++;
		if(count >= (unsigned)arraylen) {
			break;
		}
	}
	iCurPos = iPos;

	return count;
}
